/*
 * JavaCard software implementation of decoding for OAEP scheme.
 * Based on source code from BouncyCastle (www.bouncycastle.org)
 * Ported by Petr Svenda http://www.svenda.com/petr

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

   1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
   2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
   3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
USAGE:
  // allocate OAEP engine
  JC_OAEP oaep = new JC_OAEP();
  // initialze cipher engine (e.g., RSACipher), hash engine (e.g., Sha1), optional encoding parameters (can be null, if not used),
  // NOTE: external_array_for_internal_work_can_be_null ... array used for internal computations (if null then array will be allocated interally) - should be RAM array for reasonable speed. you can reuse existing array.
  // if you will use let array to be allocated internally, set MAX_MASK_ARRAY_LENGTH to proper value (depending on cipher modulus - 200B is fine for RSA 2048 with SHA2-512)
  oaep.init(false, cipher_engine, hash_engine, optional_encoding_parameters_can_be_null, external_array_for_internal_work_can_be_null);

  // decode block of data from OAEP encoding
  decLen = oaep.decodeBlock(data_to_decode, start_offset_of_data, length_of_data);
 */

package oaep;
import javacard.framework.*;
import javacard.security.*;
import javacardx.crypto.*;
//import org.bouncycastle.crypto.engines.RSAEngine;
//import org.bouncycastle.crypto.Digest;
//import java.security.SecureRandom;


/**
 * JavaCard software implemnetation of OAEP padding. Based on BouncyCastle implementation (www.bouncycastle.org).
 * Ported by Petr Svenda (petr@svenda.com)
 * Note: code still contains several parts of code commented out - this is intensional for debugging purposes in standard Java debugger (outside card, no JavaCard)
 * @author Petr Svenda
*/
// TODO:
// 1. Add function for temporar storage of internal state of Sha-512 to bypass repeated processing of the same data
// 2. Remove necessity for separate array for mask source data storage in decode() - is it possible?
// 3. Allow to set external RAM array

public class JC_OAEP {
  private final static boolean RSA_NOPAD_USED = true;
  private final static short MAX_MASK_ARRAY_LENGTH = (short) 200; // 200 is maximum value required if RSA2048bits OAEP is used

  public static final short OAEP_DECODE_FAIL                   = (short) 0x6003;

    //RSAEngine                       engine;
    Cipher                       engine;
    private byte[]               defHash;
    private byte[]               tempHash;
    private byte[]               C;
    byte[]                       maskSource;

    //private Digest                  hash;
    //private Digest                  mgf1Hash;
    private MessageDigest        hash;
    private MessageDigest        mgf1Hash;
    private boolean              forEncryption;

    public void init(
//        boolean             forEncryption,
        Cipher              engine,
        MessageDigest       hash,
        byte[]              encodingParams,
        byte[]              externalMaskSourceArray)
    {
        this.engine = engine;
        this.hash = hash;
        this.mgf1Hash = hash;
//        this.forEncryption = forEncryption;
        tempHash = new byte[hash.getLength()];
        //tempHash = JCSystem.makeTransientByteArray(hash.getLength(), JCSystem.CLEAR_ON_RESET);
        defHash = new byte[hash.getLength()];
        //defHash = JCSystem.makeTransientByteArray(hash.getLength(), JCSystem.CLEAR_ON_RESET);
        C = new byte[4];
        //C = JCSystem.makeTransientByteArray((byte) 4, JCSystem.CLEAR_ON_RESET);
        if (externalMaskSourceArray == null) maskSource = JCSystem.makeTransientByteArray(MAX_MASK_ARRAY_LENGTH, JCSystem.CLEAR_ON_RESET);
        else maskSource = externalMaskSourceArray;
        if (encodingParams != null) {
            hash.doFinal(encodingParams, (short) 0, (short) encodingParams.length, defHash, (short) 0);
        }
        else hash.doFinal(encodingParams, (short) 0, (short) 0, defHash, (short) 0);
    }
/**/
/* // Use this function for pure-Java debugging (outside card, no javaCard)
    public void init(
        boolean             forEncryption,
        RSAEngine           engine,
        Digest              hash,
        byte[]              encodingParams)
    {
        this.engine = engine;
        this.hash = hash;
        this.mgf1Hash = hash;
        this.forEncryption = forEncryption;
        defHash = new byte[hash.getDigestSize()];
        tempHash = new byte[hash.getDigestSize()];
        maskSource = new byte[200]; // todo: 200 is maximum value we will be using with our implementation of RSA2048 OAEP
        C = new byte[4];
        if (encodingParams != null)
        {
            hash.update(encodingParams, 0, encodingParams.length);
        }

        hash.doFinal(defHash, 0);
    }
/**/
    public short decodeBlock(
            byte[]    in,
            short     inOff,
            short     inLen)  throws ISOException
        {
            short decLen = engine.doFinal(in, inOff, inLen, in, inOff);

            // WE MUST REMOVE TRAILING ZEROES FROM DECRYPTED RESULT
            // IF RSA_PKCS1 IS USED, THAN IT WAS ALREADY PERFORMED
            // IF RSA_NOPAD IS USED, REMOVE FIRST BYTE
            if (RSA_NOPAD_USED) {
              if (in[inOff] != 0) {
                throw new ISOException(OAEP_DECODE_FAIL);
              }
              else {
                inOff++; decLen--;
              }
            }

            if (decLen < (short) ((2 * defHash.length) + 1))
            {
                throw new ISOException(OAEP_DECODE_FAIL);
            }


            //
            // unmask the seed.
            //
            short maskSourceLength = (short) (decLen -  (short) defHash.length);
            //byte[] maskSource = new byte[maskSourceLength];
            Util.arrayCopyNonAtomic(in, (short) (inOff + defHash.length), maskSource, (short) 0, (short) maskSourceLength);
            maskGeneratorFunction1(maskSource, (short) 0, maskSourceLength, (short) defHash.length, in, inOff);


            //
            // unmask the message block.
            //
            maskSourceLength = (short) defHash.length;
            Util.arrayCopyNonAtomic(in, inOff, maskSource, (short) 0, (short) maskSourceLength);
            maskGeneratorFunction1(maskSource, (short) 0, maskSourceLength, (short) (decLen - defHash.length), in, (short) (inOff + defHash.length));

            //
            // check the hash of the encoding params.
            //
            for (short i = 0; i != defHash.length; i++) {
                if (defHash[i] != in[(short) (inOff + defHash.length + i)]) {
                    throw new ISOException(OAEP_DECODE_FAIL);
                }
            }

            //
            // find the data block
            //
            short start;

            for (start = (short) (inOff + 2 * defHash.length); start < (short) (inOff + decLen); start++) {
                if (in[start] == 1 || in[start] != 0) break;
            }

            if (start >= (short) (inOff + decLen - 1)) throw new ISOException(OAEP_DECODE_FAIL);
            if (in[start] != 1) throw new ISOException(OAEP_DECODE_FAIL);

            start++;

            //
            // extract the data block
            //
            short outputLength = (short) (decLen - start);
            start -= inOff;

            if (RSA_NOPAD_USED) {
              for (short i = inOff; i < (short) (inOff + outputLength); i++) in[(short) (i - 1)] = in[(short) (start + i)];
              outputLength--;
            }
            else {
              for (short i = inOff; i < (short) (inOff + outputLength); i++) in[i] = in[(short) (start + i)];
            }
            return outputLength;
            /**/
        }

        private short maskGeneratorFunction1(
            byte[]  Z,
            short   zOff,
            short   zLen,
            short   length,
            byte[]  bufferToMask,
            short   bufferToMaskOffset)
        {
            short counter = 0;

            hash.reset();
            mgf1Hash.reset();

            // COPY INPUT

            // ASSUMPTION: WE WILL NOT PROCESS MORE THEN 128 BLOCKS
            C[0] = (byte)0;
            C[1] = (byte)0;
            C[2] = (byte)0;
            C[3] = (byte)-1;
            //for (counter = 0; counter < (short) (length / tempHash.length); counter++) {
            do {
            	C[3] = (byte) (C[3] + 1);

                mgf1Hash.update(Z, zOff, zLen);
                mgf1Hash.doFinal(C, (short) 0, (short) C.length, tempHash, (short) 0);
                //mgf1Hash.update(C, 0, C.length);
                //mgf1Hash.doFinal(tempHash, 0);

                // MASK/UNMASK PART OF GIVEN ARRAY
                for (short i = 0; i < (short) tempHash.length; i++) bufferToMask[(short) (bufferToMaskOffset + i)] ^= tempHash[i];
                bufferToMaskOffset += (short) (tempHash.length);
            }
            while (++counter < (short) (length / tempHash.length));

            if ((short) (counter * tempHash.length) < length)
            {
               	C[3] = (byte) (C[3] + 1);

                mgf1Hash.update(Z, zOff, zLen);
                mgf1Hash.doFinal(C, (short) 0, (short) C.length, tempHash, (short) 0);
                //mgf1Hash.update(C, 0, C.length);
                //mgf1Hash.doFinal(tempHash, 0);

                for (short i = 0; i < (short) tempHash.length; i++) bufferToMask[(short) (bufferToMaskOffset + i)] ^= tempHash[i];
                bufferToMaskOffset += (short) tempHash.length;
            }
            return (short) 0;
        }
}
